Repository navigation
fix(discord-bridge): honor SUTANDO_WORKSPACE for REPO resolution - #708
Conversation
When src/ is a symlink into a packaged Sutando.app bundle, Path(__file__).resolve().parent.parent returns the bundle root rather than the user's workspace. That stranded owner DMs on 2026-05-14 — bridge wrote tasks to bundle-tasks/ while core watched workspace-tasks/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to the discord-bridge fix — clarifies that this var is only needed for app-bundle installs where src/ is symlinked, not a normal git-clone setup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chetanunadkat
left a comment
There was a problem hiding this comment.
Fix looks right — the diff is small, the fallback preserves the existing behavior for non-bundle installs, and the rationale comment captures the failure mode well.
One follow-up needed
src/telegram-bridge.py:16 has the identical pattern:
REPO = Path(__file__).resolve().parent.parentSo Telegram-bridge will hit the same bundle/tasks/ vs workspace/tasks/ divergence under the app-bundle install. The fix from this PR should be ported there in the same commit (or a follow-up PR explicitly noting it). Otherwise the next "owner DM stranded" report will be Telegram-flavored.
Quick grep confirms no other bridge has this pattern — voice-agent.ts, task-bridge.ts, whatsapp-bridge.ts resolve paths differently and don't appear affected.
Minor nits (optional)
-
import osplacement: it's added inline at line ~25 afterimport discord. Conventionally goes with the stdlib imports near the top. Doesn't affect behavior, just consistency. -
.env.examplemissing trailing newline: the diff shows\ No newline at end of fileat the EOF. Worth re-saving with a final newline so future appends don't sneak onto the same line. -
Silent fallback on bad paths: if
SUTANDO_WORKSPACE=/typo/pathis set,Path(_workspace_env)succeeds and the bridge proceeds —mkdirSyncwill create the typo'd dir on first write, and owner DMs will land in/typo/path/tasks/with no warning. A startup-timeif not REPO.exists(): logger.warning(...)would catch this. Not blocking — just helpful when someone makes a typo in.env.
sonichi
left a comment
There was a problem hiding this comment.
LGTM on the core fix — env-var override + fallback is clean and well-commented (the comment block explaining the 2026-05-14 divergence is helpful for future spelunkers).
One scope concern: src/telegram-bridge.py:16 and src/dm-result.py:35 use the same Path(__file__).resolve().parent.parent pattern and would diverge identically under the same app-bundle/symlink setup. If you intend SUTANDO_WORKSPACE to fix workspace routing for the whole bridge surface, the telegram-bridge needs the same treatment — otherwise Telegram users in the same app-bundle deployment will hit the bug again.
Nit: missing newline at end of .env.example (the \\ No newline at end of file in the diff).
Concrete suggestion: either (a) expand this PR to also patch telegram-bridge.py + dm-result.py, or (b) ship as-is and file a follow-up. I'd lean (a) — it's the same one-liner pattern.
— MacBook Sutando bot (cc @sonichi for decision)
…720) (#722) * fix(bridges): honor SUTANDO_WORKSPACE in telegram-bridge + dm-result (#720) Mirror the PR #708 pattern in the two remaining files that use the same Path(__file__).resolve().parent.parent REPO resolution. Same app-bundle symlink bug stranded owner DMs on 2026-05-14; this closes the bridge-side gap for Telegram users. Smoke-tested both files: SUTANDO_WORKSPACE=/tmp/x → REPO=/tmp/x; unset → workspace fallback unchanged. Closes #720. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(bridges): strip + expanduser SUTANDO_WORKSPACE (Mini review) Addresses two non-blocking edges Mini flagged on PR #722 review: 1. Empty-string SUTANDO_WORKSPACE= produced Path("") = Path(".") (cwd misroute). Now os.environ.get(..., "").strip() returns "" which short-circuits to the workspace fallback. 2. No ~ expansion meant SUTANDO_WORKSPACE=~/work resolved to a literal "~/work" directory. Now Path(_workspace_env).expanduser() handles it. Smoke-tested all 4 cases: unset / "" / " " / "~/work" / "/tmp/test" — all resolve correctly. Same single-line shape; same fallback behavior unchanged for the git-clone default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the same two robustness guards from PR #722 (Mini's review) into discord-bridge.py: 1. Empty/whitespace SUTANDO_WORKSPACE= → falls back to workspace default (was: Path("") = Path(".") = cwd misroute). 2. SUTANDO_WORKSPACE=~/work → expands to home (was: literal "~/work"). Same one-line shape change as #722. Smoke-tested 5 cases (unset / "" / " " / "~/work" / "/tmp/test") all resolve correctly. Default behavior unchanged for the git-clone setup that #708 originally targeted. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bridges (discord-bridge.py, telegram-bridge.py, dm-result.py) all honor $SUTANDO_WORKSPACE for their tasks-dir resolution — see PRs #708/#720/#722/#723. But watch-tasks-stream.sh was still defaulting to the repo-relative $(dirname $0)/../tasks, so when Sutando.app sets SUTANDO_WORKSPACE (which it always does), the bridges write tasks to the workspace but the watcher polls the repo. Owner DMs land but the agent never sees them. Diagnosed live 2026-05-15: three Discord DMs were stranded in $SUTANDO_WORKSPACE/tasks/ for ~17 min before the workaround (restart the Monitor with an explicit positional arg). Resolution priority is now: 1. explicit positional arg (back-compat) 2. $SUTANDO_WORKSPACE/tasks (matches bridges) 3. repo-relative fallback (matches old default for non-workspace setups) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#730) `src/session-handoff.sh` hard-coded `REPO="$HOME/Desktop/sutando"`. On machines where the workspace lives elsewhere (e.g. `~/Documents/github/ sutando` — Chi's primary node), the script silently no-ops: the output redirect on line 78 (`> "$STATE_FILE" 2>/dev/null`) eats the "no such file or directory" error and PreCompact never produces a snapshot. Same env-resolution pattern that #708 / #720+#722 / #723 already established for the Discord/Telegram bridges. One-line change. CLAUDE.md ("Session Continuity") explicitly relies on this file: "Read this file at session start to understand what the previous session was doing." With the bug, that signal is dead on any host that doesn't use `~/Desktop/sutando`. Smoke-tested locally: with `SUTANDO_WORKSPACE=$PWD` the script writes session-state.md to the expected location and the System Status / Recent Work / Open PRs / Pending Questions sections all populate. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Chi Wang <4250911+sonichi@users.noreply.github.com>
…s.cwd() (closes #713) (#757) src/web-client.ts used bare relative paths in three spots: state/voice-mode.txt tasks/${taskId}.txt # /paidsubscriptions/scan trigger skills/subscription-scanner/state/subscriptions.json # x2 All three are resolved against `process.cwd()` at read/write time. Under bundle/launchd/symlink installs where the CWD at process-launch isn't the workspace root, every relative path either: - silently ENOENT on read (voice-mode → wrong default), or - writes the trigger task into bundle/tasks/ where the proactive loop isn't watching (paidsubscriptions scan stranded). This is the same failure mode PR #708 just fixed for discord-bridge. Fix (Pattern A from the issue): add top-of-file REPO_DIR + TASK_DIR + STATE_DIR + SUBSCRIPTIONS_PATH constants mirroring task-bridge.ts, then route every workspace-relative path through join(REPO_DIR, ...). REPO_DIR honors SUTANDO_WORKSPACE env var first and falls back to the script's parent dir via fileURLToPath(import.meta.url). In the common case where startup.sh `cd`'s into the workspace before launching web-client, behavior is unchanged. The fix only matters when CWD diverges from the workspace. Pattern B (centralize task-file writes in task-bridge.ts) is the cleaner endgame but couples this to #695. Pattern A is the contained, no-blocker fix. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ichi#708) * fix(discord-bridge): honor SUTANDO_WORKSPACE for REPO resolution When src/ is a symlink into a packaged Sutando.app bundle, Path(__file__).resolve().parent.parent returns the bundle root rather than the user's workspace. That stranded owner DMs on 2026-05-14 — bridge wrote tasks to bundle-tasks/ while core watched workspace-tasks/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(env): explain when to set SUTANDO_WORKSPACE Companion to the discord-bridge fix — clarifies that this var is only needed for app-bundle installs where src/ is symlinked, not a normal git-clone setup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onichi#720) (sonichi#722) * fix(bridges): honor SUTANDO_WORKSPACE in telegram-bridge + dm-result (sonichi#720) Mirror the PR sonichi#708 pattern in the two remaining files that use the same Path(__file__).resolve().parent.parent REPO resolution. Same app-bundle symlink bug stranded owner DMs on 2026-05-14; this closes the bridge-side gap for Telegram users. Smoke-tested both files: SUTANDO_WORKSPACE=/tmp/x → REPO=/tmp/x; unset → workspace fallback unchanged. Closes sonichi#720. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(bridges): strip + expanduser SUTANDO_WORKSPACE (Mini review) Addresses two non-blocking edges Mini flagged on PR sonichi#722 review: 1. Empty-string SUTANDO_WORKSPACE= produced Path("") = Path(".") (cwd misroute). Now os.environ.get(..., "").strip() returns "" which short-circuits to the workspace fallback. 2. No ~ expansion meant SUTANDO_WORKSPACE=~/work resolved to a literal "~/work" directory. Now Path(_workspace_env).expanduser() handles it. Smoke-tested all 4 cases: unset / "" / " " / "~/work" / "/tmp/test" — all resolve correctly. Same single-line shape; same fallback behavior unchanged for the git-clone default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the same two robustness guards from PR sonichi#722 (Mini's review) into discord-bridge.py: 1. Empty/whitespace SUTANDO_WORKSPACE= → falls back to workspace default (was: Path("") = Path(".") = cwd misroute). 2. SUTANDO_WORKSPACE=~/work → expands to home (was: literal "~/work"). Same one-line shape change as sonichi#722. Smoke-tested 5 cases (unset / "" / " " / "~/work" / "/tmp/test") all resolve correctly. Default behavior unchanged for the git-clone setup that sonichi#708 originally targeted. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…chi#736) The bridges (discord-bridge.py, telegram-bridge.py, dm-result.py) all honor $SUTANDO_WORKSPACE for their tasks-dir resolution — see PRs sonichi#708/sonichi#720/sonichi#722/sonichi#723. But watch-tasks-stream.sh was still defaulting to the repo-relative $(dirname $0)/../tasks, so when Sutando.app sets SUTANDO_WORKSPACE (which it always does), the bridges write tasks to the workspace but the watcher polls the repo. Owner DMs land but the agent never sees them. Diagnosed live 2026-05-15: three Discord DMs were stranded in $SUTANDO_WORKSPACE/tasks/ for ~17 min before the workaround (restart the Monitor with an explicit positional arg). Resolution priority is now: 1. explicit positional arg (back-compat) 2. $SUTANDO_WORKSPACE/tasks (matches bridges) 3. repo-relative fallback (matches old default for non-workspace setups) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nichi#722/sonichi#723) (sonichi#730) `src/session-handoff.sh` hard-coded `REPO="$HOME/Desktop/sutando"`. On machines where the workspace lives elsewhere (e.g. `~/Documents/github/ sutando` — Chi's primary node), the script silently no-ops: the output redirect on line 78 (`> "$STATE_FILE" 2>/dev/null`) eats the "no such file or directory" error and PreCompact never produces a snapshot. Same env-resolution pattern that sonichi#708 / sonichi#720+sonichi#722 / sonichi#723 already established for the Discord/Telegram bridges. One-line change. CLAUDE.md ("Session Continuity") explicitly relies on this file: "Read this file at session start to understand what the previous session was doing." With the bug, that signal is dead on any host that doesn't use `~/Desktop/sutando`. Smoke-tested locally: with `SUTANDO_WORKSPACE=$PWD` the script writes session-state.md to the expected location and the System Status / Recent Work / Open PRs / Pending Questions sections all populate. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Chi Wang <4250911+sonichi@users.noreply.github.com>
…s.cwd() (closes sonichi#713) (sonichi#757) src/web-client.ts used bare relative paths in three spots: state/voice-mode.txt tasks/${taskId}.txt # /paidsubscriptions/scan trigger skills/subscription-scanner/state/subscriptions.json # x2 All three are resolved against `process.cwd()` at read/write time. Under bundle/launchd/symlink installs where the CWD at process-launch isn't the workspace root, every relative path either: - silently ENOENT on read (voice-mode → wrong default), or - writes the trigger task into bundle/tasks/ where the proactive loop isn't watching (paidsubscriptions scan stranded). This is the same failure mode PR sonichi#708 just fixed for discord-bridge. Fix (Pattern A from the issue): add top-of-file REPO_DIR + TASK_DIR + STATE_DIR + SUBSCRIPTIONS_PATH constants mirroring task-bridge.ts, then route every workspace-relative path through join(REPO_DIR, ...). REPO_DIR honors SUTANDO_WORKSPACE env var first and falls back to the script's parent dir via fileURLToPath(import.meta.url). In the common case where startup.sh `cd`'s into the workspace before launching web-client, behavior is unchanged. The fix only matters when CWD diverges from the workspace. Pattern B (centralize task-file writes in task-bridge.ts) is the cleaner endgame but couples this to sonichi#695. Pattern A is the contained, no-blocker fix. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
src/discord-bridge.py: whenSUTANDO_WORKSPACEis set, resolveREPOfrom that path instead ofPath(__file__).resolve().parent.parent. Fixes the bundle/workspace divergence that stranded owner DMs on 2026-05-14 — bridge wrote tasks to bundle-tasks/ while core watched workspace-tasks/..env.example: document when to setSUTANDO_WORKSPACE(app-bundle installs where src/ is symlinked; leave unset for normal git-clone).Test plan
SUTANDO_WORKSPACEin.env, restart bridge, DM the bot → task lands in the workspacetasks/, not the bundle copySUTANDO_WORKSPACE, restart bridge → falls back to the previousPath(__file__).resolve().parent.parentbehavior